#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
//for memset()
#include <string.h>
#include <string>
#include <pthread.h>
#include <vector>

using std::string;
using std::vector;

struct connectionInfo 
{
	int socket_fd;
	int MYPORT;
	int BACKLOG;
	struct sockaddr_in my_address;
};

struct messageInfo
{
	int dest;
	string source;
	string msg;
};

struct threadArgs
{
	int fd;
	int threadNum;
	struct sockaddr_in c_addr;
	struct connectionInfo conn;
};

pthread_mutex_t messageLock;
vector<messageInfo> theMessages;
int	numCreatedThreads = 0; 
//incremented by worker threads just before the thread has finished
//servicing an incoming connection
int numTerminatedThreads = 0;
//function that each thread will execute
void* server_comm(void *theArgs);
string getMessage(int threadNum);
//sets up the server port to listen to
int setUpServerComm(struct connectionInfo &);

int main(int argc, char** argv)
{
	struct connectionInfo connection;
	
	
	connection.MYPORT  = atoi(argv[1]);
	connection.BACKLOG = atoi(argv[2]);
	int numClients = atoi(argv[3]);
	
	int client_fd, error; 
	//incremented by the master thread everytime a new server connection
	//is requested. needed to get out of while loop
	struct sockaddr_in client_address;
	socklen_t sin_size;
	pthread_mutex_init(&messageLock, NULL);
	
	
	if((setUpServerComm(connection)) == -1)
	{
		return 1;
	}
	
	do
	{
		if(listen(connection.socket_fd, connection.BACKLOG) == -1)
		{
			perror("listen() failed to create a socket!\n");
			return 1;
		}
		
		sin_size = sizeof(struct sockaddr_in);

		client_fd = accept(connection.socket_fd, (struct sockaddr *)&client_address, &sin_size);

		if(client_fd == -1)
		{
		  perror("accept() error lol!");
		  return 1;
		}
		else
		  printf("accept() is OK...\n");
		  
		struct threadArgs tempArgs;
		tempArgs.fd = client_fd;
		tempArgs.threadNum = numCreatedThreads + 1;
		tempArgs.c_addr = client_address;
		tempArgs.conn = connection;
		
		pthread_t tempThread;
		int success = pthread_create(&tempThread, NULL, server_comm, (void *)&tempArgs);
		numCreatedThreads++;
	}while(numCreatedThreads < numClients);
	sleep(5);
	
	close(connection.socket_fd);
	pthread_mutex_destroy(&messageLock);
	
	return 0;
}

int setUpServerComm(struct connectionInfo &connection)
{
	if((connection.socket_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
	{
		perror("socket() failed to create a socket!\n");
		return -1;
	}
	else
	{
		printf("socket() executed successfully!\n");
	}
	
	//define communication type as TCP or UDP
	connection.my_address.sin_family      = AF_INET;
	//define which port to listen in on
	connection.my_address.sin_port        = htons(connection.MYPORT);
	connection.my_address.sin_addr.s_addr = INADDR_ANY;
	
	memset(&(connection.my_address.sin_zero),0,8);
	
	if((bind(connection.socket_fd, (struct sockaddr *)&connection.my_address,sizeof(struct sockaddr))) == -1)
	{
		perror("binding to socket failed\n");
		return -1;
	}
	else
	{
		printf("binding was successful\n");
	}
	return 1;
}

void * server_comm(void * theArgs)
{
	struct threadArgs args = *((struct threadArgs *) theArgs);
	char qbuffer[1024];
	int bytes = 0;
	memset (qbuffer, 0, 1024 * sizeof(char ));
	do
	{
		bytes = recv(args.fd, qbuffer, 1024, 0);
	}while(bytes <= 0);
	if (bytes < 0)
		perror("ERROR reading from socket");
	
	string client = qbuffer;
	
	string timeStamp = client.substr(0, 19);
	string clientName = client.substr(19, 2);
	string destName = client.substr(21, 2);
	string clientMessage = client.substr(23, client.length());
	
	struct messageInfo tempMessageInfo;
	tempMessageInfo.source = clientName;
	tempMessageInfo.msg = clientMessage;
	tempMessageInfo.dest = atoi(destName.substr(1, destName.length()).c_str());
	args.threadNum = atoi(clientName.substr(1, clientName.length()).c_str());
	
	theMessages.push_back(tempMessageInfo);
	
	printf("Message from client %s at address %s on port %d: %s \n", clientName.c_str(),
			inet_ntoa(args.c_addr.sin_addr), ntohs(args.c_addr.sin_port), clientMessage.c_str());

	pthread_mutex_lock(&messageLock);
	string temp = "Message from ";
	string temp2 = " at ";
	string temp3 = ": ";
	string message = temp + destName + temp2 + timeStamp + temp3 + getMessage(args.threadNum);
	pthread_mutex_unlock(&messageLock);

	bytes = send(args.fd, message.c_str(), message.length(), 0);
	numTerminatedThreads++;

	close(args.fd);
	pthread_exit(NULL);
}


string getMessage(int threadNum)
{
	struct messageInfo temp;
	unsigned int i;
	bool noMessages = false;
	do
	{
		if(theMessages.size() != 0)
		{
			for(i = 0; i < theMessages.size(); i++)
			{

				if(theMessages[i].dest == threadNum)
				{
					temp = theMessages[i];
					theMessages.erase(theMessages.begin()+i);
					noMessages = true;
				}
			}
		}
	}while(!noMessages);
	
	return temp.msg;
}
